package com.bear.newsset.util;

import android.annotation.SuppressLint;

import androidx.annotation.AnimRes;
import androidx.annotation.AnimatorRes;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;

import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;

/**
 * Fragment管理器
 * 管理器主要用于切换父容器中的Fragment，切换时需要一个父容器和待切换的Fragment
 * @see #switchFragment(Fragment, int, int)
 * @see #switchFragment(Fragment)
 * @see #switchFragment(Class, int, int)
 * @see #switchFragment(Class)
 *
 * @see #popBackStack()
 * @see #setSingleBackStack(boolean)
 *
 * @see #setFragmentActivity(FragmentActivity)
 * @see #setDefaultAnimations(int, int)
 * @see #setSuperLayoutID(int)
 *
 * @see #getFragmentActivity()
 * @see #getCurrentFragment()
 * @see #getFragmentManagerV4()
 * @see #getFragmentTransaction()
 *
 * 内部使用的私有方法
 * @see #queryFragment(Class)
 * @see #newClass(Class)
 */
public class FragmentManage {
    private FragmentActivity mFragmentActivity;
    @IdRes
    private int superLayoutID;
    @AnimatorRes
    @AnimRes
    private int animResIn, animResOut;
    private FragmentManager fm;
    private FragmentTransaction ft;
    //如果传入Fragment传入时，它会成为当前显示的Fragment
    private Fragment currentFragment;
    private boolean isSingleBackStack;

    private FragmentManage() {
        initDefaultAnimations();
        isSingleBackStack = false;
        currentFragment = null;
    }
    public static FragmentManage getInstance() { return HANDLER.INSTANCE; }
    private static final class HANDLER {
        @SuppressLint("StaticFieldLeak")    /* 虚引用，不会出现内存泄漏，最多为null */
        private static final FragmentManage INSTANCE = new FragmentManage();
    }

    /**
     * 查询Fragment，如果不存在则创建一个Fragment返回
     * @param cls   查询Fragment的类
     * @param <F>   泛型为Fragment
     * @return      返回查询到的Fragment
     */
    private <F extends Fragment> Fragment queryFragment(@NonNull Class<F> cls) {
        for( Fragment f : fm.getFragments() ) {
            if( f.getClass().getSimpleName().equals( cls.getSimpleName() ) ) return f;
        }
        return newClass( cls );
    }

    /**
     * 创建一个Fragment
     * @param cls   创建的class
     * @param <T>   泛型必须是Fragment
     * @return      返回创建好的Fragment
     */
    public <T extends Fragment> T newClass(Class<T> cls) {
        T tCls = null;
        try {
            tCls = cls.getConstructor().newInstance();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return tCls;
    }

    /**
     * 切换Fragment必须使用的FragmentActivity
     * 最好在onResume或者onSaveInstanceState的时候调用一次，防止抛异常
     * *抛异常：因为是使用commitAllowingStateLoss提交的，所以不会抛异常。
     * 异常：IllegalStateException: Can not perform this action after onSaveInstanceState
     * @param fa            Activity
     * @return              {@link FragmentManage}
     */
    public <T extends FragmentActivity>FragmentManage setFragmentActivity(@NonNull T fa) {
        this.mFragmentActivity = new WeakReference<>(fa).get();
        return this;
    }

    /**
     * 获取虚引用的Activity
     * @return      Activity
     */
    @Nullable
    public FragmentActivity getFragmentActivity() {
        return mFragmentActivity;
    }

    /**
     * 设置父布局容器id
     * @param superLayoutID     布局id
     * @return                  {@link FragmentManage}
     */
    public FragmentManage setSuperLayoutID(@IdRes int superLayoutID) {
        this.superLayoutID = superLayoutID;
        return this;
    }

    /**
     * 设置默认的初始动画
     * @param animResIn     进入动画
     * @param animResOut    退出动画
     * @return              {@link FragmentManage}
     */
    public FragmentManage setDefaultAnimations(@AnimatorRes @AnimRes int animResIn,
                                               @AnimatorRes @AnimRes int animResOut) {
        this.animResIn = animResIn;
        this.animResOut = animResOut;
        return this;
    }

    /**
     * 初始化默认的动画
     * @return              {@link FragmentManage}
     */
    public FragmentManage initDefaultAnimations() {
        animResIn = -1;
        animResOut = -1;
        return this;
    }

    /**
     * 获取当前的Fragment
     * @return              正在显示的Fragment
     */
    public Fragment getCurrentFragment() {
        return currentFragment;
    }

    /**
     * 获取v4包自带的Fragment管理器
     * @return              {@link FragmentManager}
     */
    public FragmentManager getFragmentManagerV4() {
        return fm;
    }

    /**
     * 获取v4包自带的Fragment事务
     * @return              {@link FragmentTransaction}
     */
    public FragmentTransaction getFragmentTransaction() {
        return ft;
    }

    /**
     * 之前添加的Fragment按队列退栈
     */
    public void popBackStack() {
        fm.popBackStack();
    }

    /**
     * 只退栈最开始的顺序
     * @param isSingleBackStack 每个栈是否只退一次
     * @return                  {@link FragmentTransaction}
     */
    public FragmentManage setSingleBackStack(boolean isSingleBackStack) {
        this.isSingleBackStack = isSingleBackStack;
        return this;
    }

    /**
     * 切换Fragment
     * @param cls           需要切换的Fragment
     * @param in            进入动画
     * @param out           离开动画
     */
    public <F extends Fragment>
    void switchFragment(Class<F> cls, @AnimatorRes @AnimRes int in, @AnimatorRes @AnimRes int out) {
        if( cls == null || mFragmentActivity == null ) return;
        //通过自带的FM、FT实现切换
        fm = mFragmentActivity.getSupportFragmentManager();
        ft = fm.beginTransaction();
        //查询传入的Fragment，如果不存在则创建一个返回
        Fragment to = queryFragment( cls );
        //添加动画
        if( in != -1 && out != -1 ) ft.setCustomAnimations(in, out, in, out);
        //隐藏当前显示的Fragment
        if( currentFragment != null ) ft.hide( currentFragment );
        //切换当前的Fragment，如果已经存在则先隐藏，在显示
        if( !to.isAdded() ) {
            ft.add( superLayoutID, to );
            //只退栈一次
            if( isSingleBackStack ) ft.addToBackStack(null);
        }else if( to.isHidden() ){
            ft.show( to );
        }
        //允许多次退栈
        if( !isSingleBackStack ) ft.addToBackStack(null);
        ft.commitAllowingStateLoss();
        currentFragment = to;
    }

    public <F extends Fragment>
    void switchFragment(FragmentActivity fa, Class<F> cls, @AnimatorRes @AnimRes int in, @AnimatorRes @AnimRes int out) {
        if( cls == null || fa == null ) return;
        //通过自带的FM、FT实现切换
        fm = fa.getSupportFragmentManager();
        ft = fm.beginTransaction();
        //查询传入的Fragment，如果不存在则创建一个返回
        Fragment to = queryFragment( cls );
        //添加动画
        if( in != -1 && out != -1 ) ft.setCustomAnimations(in, out, in, out);
        //隐藏当前显示的Fragment
        if( currentFragment != null ) ft.hide( currentFragment );
        //切换当前的Fragment，如果已经存在则先隐藏，在显示
        if( !to.isAdded() ) {
            ft.add( superLayoutID, to );
            //只退栈一次
            if( isSingleBackStack ) ft.addToBackStack(null);
        }else if( to.isHidden() ){
            ft.show( to );
        }
        //允许多次退栈
        if( !isSingleBackStack ) ft.addToBackStack(null);
        //不使用commit是防止抛异常，该方法只是跳过了状态验证
        ft.commitAllowingStateLoss();
        currentFragment = to;
    }

    /**
     * 切换Fragment
     * @param to            需要切换的Fragment的class
     */
    public <F extends Fragment>
    void switchFragment(Class<F> to) {
        switchFragment(to, animResIn, animResOut);
    }

    /**
     * 切换Fragment
     * @param to            需要切换的Fragment
     * @param in            进入动画
     * @param out           离开动画
     */
    public void switchFragment(@NonNull Fragment to,
                               @AnimatorRes @AnimRes int in, @AnimatorRes @AnimRes int out) {
        switchFragment(to.getClass(), in, out);
    }

    /**
     * 切换Fragment
     * @param to            需要切换的Fragment
     */
    public void switchFragment(@NonNull Fragment to) {
        switchFragment( to.getClass(), animResIn, animResOut);
    }
}